<?php
/**
 *
 *
 *
 */
abstract class Yz_Template_Abstract
    extends Yz_Class
    implements Yz_Template_Interface
{

    /** constants to facilitate JQUERY and YZ includes
     *
     */
    const JQUERY_JS     = './www/jquery/js/jquery.js';
    const JQUERY_UI_JS  = './www/jquery/js/jquery-ui.js';
    const JQUERY_UI_CSS = './www/jquery/css/jquery-ui.css';
    const YZ_JS         = './www/Yz/js/Yz.js';

    /** The template's name
     *  @var string
     */
    protected $name;

    /** The template's page title
     *  @var string
     */
    protected $title;

    /** The storage for assigned variables
     *  @var array
     */
    protected $value = array();

    /** Reference to the module
     *  @var Yz_Module_Interface
     */
    protected $module;

    /** Reference to the i18n
     *  @var Yz_I18N_Interface
     */
    protected $i18n;

    /** Meta tags for the template
     *  @var array
     */
    protected $meta = array(
        self::META_DESCRIPTION => '',
        self::META_KEYWORDS => '',
        );

    /** Scripts added via include
     *  @var array
     */
    protected $js = array();

    /** Styles added via includeCSS
     *  @var array
     */
    protected $css = array();

    /** Included templates added via includeTemplate
     *  @var array
     */
    protected $templates = array();

    /** Included forms added via includeForm
     *  @var array
     */
    protected $forms = array();

    /** layout template
     *  @var Yz_Template_Interface
     */
    protected $layoutName = 'layout';

    /** The content once rendered, if render caching is enabled
     *
     *  A value of RENDER_CACHE_EMPTY means render caching is enabled, and template not rendered yet
     *  A value of RENDER_CACHE_NONE means render caching is disabled
     *  A string value is the content already rendered
     *  @var string|bool
     */
    protected $content = self::RENDER_CACHE_EMPTY;

    /**
     *  @var bool
     */
    protected $useSystemTemplates = false;

    /****************************
     *      Public methods      *
     ****************************/


    /**
     *
     */
    public function __construct( Yz_Module_Interface $module )
    {
        debug();
        $this->module = $module;
        $this->name = lcfirst($this->shortClassName());
    }


    /** render the template or get its content if already rendered
     *
     *  @return string
     */
    public function render()
    {
        debug();
        $c =& $this->content;
        if (self::RENDER_CACHE_NONE !== $c && self::RENDER_CACHE_EMPTY !== $c) {

            // return saved content
            return $c;
        }
        $r = $this->doRender();
        if (self::RENDER_CACHE_NONE !== $c) {

            // caching enabled => save rendered content
            $c = $r;
        }
        return $r;
    }

    /**
     *  @return string
     */
    public function renderWithParent( Yz_Template_Interface $parentTemplate )
    {
        debug();

        // use parent's values
        $this->value += $parentTemplate->toArray();

        return $this->doRender();
    }

    /**
     * @return string
     */
    public function name()
    {
        return $this->name;
    }

    /**
     *  @return array
     */
    public function toArray()
    {
        return $this->value;
    }

    /**
     *  TODO lookup in module config
     *  @return string
     */
    public function selectLayout()
    {
        return $this->layoutName;
    }


    public function useSystemTemplates()
    {
        $this->useSystemTemplates = true;
        return $this;
    }

    /** Include a javascript into the HEAD
     *  @param string|null  $src serves as unique id/name
     *  @param string|null  $text ignored if $src is not empty
     *  @return chainable
     *  @throw Yz_Exception_InvalidArgument if both src and text are empty
     */
    public function includeJS($src, $text=null)
    {
        debug();
        assert('is_string($src)');
        if (null !== $text) {
            assert('is_string($text)');
        }
        $this->js[$src] = $text;
        return $this;
    }


    /** Include a stylesheet into the HEAD as a LINK or STYLE tag
     *  @param string|null  $href serves as unique id/name
     *  @param string|null  $text ignored if $href is not empty
     *  @return chainable
     *  @throw Yz_Exception_InvalidArgument if both href and text are empty
     */
    public function includeCSS($href, $text=null)
    {
        assert('is_string($href)');
        if (null !== $text) {
            assert('is_string($text)');
        }
        $this->css[$href] = $text;
        return $this;
    }

    /**
     *  @return array
     */
    public function getCSS()
    {
        return $this->css;
    }

    /**
     *  @return array
     */
    public function getJS()
    {
        return $this->js;
    }

    /**
     *  @return array
     */
    public function getTitle()
    {
        $title =& $this->title;
        if (array_key_exists('title', $this->value)) {
            $title = $this->value['title'];
        } else if (null === $title) {
            $title = $this->translate("#" . $this->classNameUnqualified());
        }
        return $title;
    }

    /**
     *  @return array
     */
    public function getMeta()
    {
        return $this->meta;
    }

    /**
     *  @return array
     */
    public function assignMeta($metaEntry, $content)
    {
        assert('is_string($metaEntry)');
        assert('is_string($content)');
        $this->meta[$metaEntry] = $content;
        return $this;
    }


    /** Include a form
     *  @param Yz_Form_Interface $form
     *  @return chainable
     */
    public function includeForm(Yz_Form_Interface $form)
    {
        debug();
        $n = strtolower($form->getName());
        debug($n);
        $this->forms[$n] = $form;
        return $this;
    }


    /** Render a form by name
     *  @param string $name
     *  @return chainable
     */
    public function renderForm($formName)
    {
        debug();
        assert('is_string($formName)');
        $n = strtolower($formName);
        if (! array_key_exists($n, $this->forms)) {
            throw new Yz_Template_Exception("Form {$n} not found");
        }
        $form = $this->forms[$n];
        return $this->doRenderForm($form);
    }


    /** Include a template
     *
     *  One template can be included many times under different names using altName
     *  @param Yz_Template_Interface $template
     *  @param string|null $altName an alternative name for template
     *  @return chainable
     */
    public function includeTemplate(
        Yz_Template_Interface $template,
        $altName=null)
    {
        debug();
        if (null !== $altName) {
            $n = Yz_String::testNotEmpty($altName);
        } else {
            $n = $template->name();
        }
        $this->templates[strtolower($n)] = $template;
        return $this;
    }


    /** Create a template given by module name and template name
     *  @param string $moduleName
     *  @param string $templateName
     *  @return Yz_Template_Interface
     *  @todo automatically look up cached templates
     */
    public function createTemplate($templateName, $moduleName=null)
    {
        debug();
        if (null === $moduleName) {
            $module = $this->getModule();
        } else {
            $module = Yz::selectModule($moduleName);
        }
        $class = $module->resolveLibrary('Template', $templateName);
        $template = new $class($module);
        return $template;
    }


    /** Render a template by name
     *  @param Yz_Template_Interface|string $template
     *  @return string rendered template
     */
    public function renderTemplate($template)
    {
        debug();
        if ($template instanceof Yz_Template_Interface) {
        } else  {
            if (! is_string($template)) {
                throw new Yz_Template_Exception("Invalid template specification");
            }
            if (! array_key_exists($template, $this->templates)) {
                throw new Yz_Template_Exception("Failed to find included template '{$template}'");
            }
            $template = $this->templates[strtolower($template)];
        }
        return $this->doRenderTemplate($template);
    }

    /**
     *
     *  @todo real translate for non-empty lang and parameters
     */
    public function translate( $msg )
    {
        return $this->getI18N()->translate($msg);
    }

    /**
     *
     *  @todo real translate for non-empty lang and parameters
     */
    public function translateWithParams( $msg, $args )
    {
        return $this->getI18N()->translateWithParams($msg, $args);
    }

    /****************************
     *    Protected methods     *
     ***************************/

    protected function indexExists( $index )
    {
        return array_key_exists( $index, $this->value );
    }

    /** defaultGetter() : override the Yz_Class
     *  @return mixed | null if index not in list
     */
    protected function defaultGetter( $index )
    {
        if ( $this->indexExists($index) ) {
            $value = $this->value[$index];
            return $value;
        }
        return null;
    }

    /** defaultSetter() : override the Yz_Class
     *  @todo check index
     *  @return this
     */
    protected function defaultSetter( $index, $value )
    {
        $this->value[$index] = $value;
        return $this;
    }


    /** Render the included Javascripts (once and all at once)
     *  @return string
     */
    protected function renderJS()
    {
        debug();
        $o = "";
        // a non-topmost template SHOULD NOT render any JS
        foreach ($this->js as $jsId => $js) {
            $o .= $this->doRenderJS($jsId, $js);
        }
        $this->js = array();
        return $o;
    }

    /** Render the included Styles (once and all at once)
     *  @return string
     */
    protected function renderCSS()
    {
        debug();
        $o = "";
        // a non-topmost template SHOULD NOT render any JS
        foreach ($this->css as $cssId => $css) {
            $o .= $this->doRenderCSS($cssId, $css);
        }
        $this->css = array();
        return $o;
    }

    /** Render the assigned Metas (once and all at once)
     *  @return string
     */
    protected function renderMeta()
    {
        debug();
        $o = "";
        // a non-topmost template SHOULD NOT render any meta
        foreach ($this->meta as $name => $content) {
            $content = htmlspecialchars($content);
            $o .= <<<html
            <meta name="{$name}" content="{$content}" />\n
html;
        }
        $this->meta = array();
        return $o;
    }


    /**
     *  @param string $name
     *  @param array $entry [0 => href, 1 => text]
     *  @return string
     */
    protected function doRenderCSS($href, $text)
    {
        debug("CSS href='$href'");
        if (null === $text) {
            if ('./' === substr($href, 0, 2)) {
                $href = $this->value['webRoot'] . substr($href, 2);
            }
            $html = <<<html
<link rel="stylesheet" href="{$href}" type="text/css" />\n
html;
        } else {
            $html = <<<html
<style type="text/css" id="yz-style-{$href}" />
/* Inserted Style: '{$href}' */
{$text}
</style>\n
html;
        }
        return $html;
    }

    /**
     *  @param string $name
     *  @param array $entry [0 => src, 1 => text]
     *  @return string
     */
    protected function doRenderJS($src, $text)
    {
        debug("JS name='$name'");
        if (null === $text) {
            if ('./' === substr($src, 0, 2)) {
                $src = $this->value['webRoot'] . substr($src, 2);
            }
            $src = " src='{$src}'";
            $text = "/* Inserted Javascript */";
        } else {
            $src = " id='yz-javascript-{$src}'";
            $text = "\n/* Inserted Javascript: {$src} */\n{$text}\n";
        }
        $html = <<<html
<script type="text/javascript" {$src}>{$text}</script>\n
html;
        return $html;
    }

    /**
     *  @param Yz_Form_Interface $form
     *  @return string
     */
    protected function doRenderForm(Yz_Form_Interface $form)
    {
        debug();
        $html = $form->render($this);
        return $html;
    }

    /**
     *  @param Yz_Template_Interface $template
     *  @return string
     */
    protected function doRenderTemplate(Yz_Template_Interface $template)
    {
        debug();
        if ($this->useSystemTemplates) {
            $template->useSystemTemplates();
        }
        $html = $template->renderWithParent($this);
        $this->css += $template->getCSS();
        $this->js  += $template->getJS();
        return $html;
    }

    protected function getI18N()
    {
        $i =& $this->i18n;
        if (null === $i) {
            $i = $this->getModule()->getI18N();
        }
        return $i;
    }

    protected function getModule()
    {
        return $this->module;

        ////////////////////
        ///   an example:
        ////////////////////
        $i =& $this->module;
        if (null === $i) {
            $i = call_user_func(static::namespaceName().'\\Module::getInstance');
        }
        return $i;
    }

}
